package net.qiqb.spring.domain.event;

import cn.hutool.core.net.NetUtil;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import net.qiqb.core.domain.event.DomainEvent;
import net.qiqb.core.domain.event.DomainEventLog;
import net.qiqb.core.domain.event.DomainEventLogPersistence;
import net.qiqb.core.domain.event.receiver.DomainEventMulticaster;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.PayloadApplicationEvent;
import org.springframework.context.event.ApplicationListenerMethodAdapter;
import org.springframework.context.event.SimpleApplicationEventMulticaster;
import org.springframework.core.ResolvableType;
import org.springframework.core.env.Environment;

import java.time.LocalDateTime;

/**
 * 改写spring 的事件分发器。主要是根据事件事件类型获取事件监听器
 */
@Slf4j
public class SpringDomainEventMulticaster extends SimpleApplicationEventMulticaster implements DomainEventMulticaster {

    private final ApplicationContext applicationContext;

    private final DomainEventLogPersistence domainEventLogPersistence;

    public SpringDomainEventMulticaster(ConfigurableListableBeanFactory beanFactory, ApplicationContext applicationContext) {
        super(beanFactory);
        this.applicationContext = applicationContext;
        this.domainEventLogPersistence = applicationContext.getBean(DomainEventLogPersistence.class);
    }

    @Override
    public int findAndMulticastEvent(DomainEvent domainEvent) {
        final Object domainEventObject = domainEvent.getPayload();
        ResolvableType type = ResolvableType.forInstance(domainEventObject);
        int i = 0;
        final PayloadApplicationEvent<Object> event = new PayloadApplicationEvent<>(this, domainEventObject);

        for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
            i++;
            // 这里需要进行查询判断是否已经在处理了，如果处理成功无需继续发送
            String domainEventLogId = null;
            try {

                String listenerId = "default";
                if (listener instanceof ApplicationListenerMethodAdapter listenerMethodAdapter) {
                    listenerId = listenerMethodAdapter.getListenerId();
                }
                final DomainEventLog domainEventLog = getDomainEventLog(domainEvent.getId(), listenerId);
                if (domainEventLog != null) {
                    domainEventLogId = domainEventLog.getId();
                    if ("fail".equals(domainEventLog.getProcessResult())) {
                        // 处理是否是否重新发送
                        log.info("领域事件发送处理失败，是否需要重复发送");
                    } else {
                        log.info("领域事件[{}]处理中，重复发送", domainEvent.getId());
                    }
                } else {
                    // 新增一个日志。新增一个日志，肯定会执行
                    final LocalDateTime beginTime = LocalDateTime.now();
                    domainEventLogId = insertDomainEventLog(domainEvent, beginTime, listenerId);
                    if (domainEventLogId != null) {
                        // 表示插入成功，可以发送
                        try {
                            // 同步执行
                            invokeListener(listener, event);
                        } catch (Throwable err) {
                            // 执行报错，本次事件处理失败
                            handleFail(domainEventLogId, beginTime, err.getMessage());
                            throw err;
                        }
                        // 理论上一个命令只能操作一个聚合根，同时和聚合根事务更新
                        // 再次查询事件是否修改成功，如果还是未处理情况，自动跟新一下成功
                        handleSuccess(domainEventLogId, beginTime);
                    }
                }
            } catch (Exception e) {
                handleFail(domainEventLogId, LocalDateTime.now(), e.getMessage());
            }

        }
        return i;
    }

    private void handleSuccess(String id, LocalDateTime beginProcessTime) {

        domainEventLogPersistence.updateSuccessById(id, beginProcessTime);
    }

    private void handleFail(String id, LocalDateTime beginProcessTime, String errorMsg) {
        domainEventLogPersistence.updateFailById(id, beginProcessTime, errorMsg);
    }

    private String insertDomainEventLog(DomainEvent domainEvent, LocalDateTime beginProcessTime, String listenerId) {
        // 根据id 和 service name 组成唯一值，如果插入成功，表示抢到了
        String serviceName = getServiceName();
        DomainEventLog domainEventLog = new DomainEventLog();
        domainEventLog.setEventId(domainEvent.getId());
        domainEventLog.setEventContent(JSONUtil.toJsonStr(domainEvent.getPayload()));
        domainEventLog.setProcessServiceName(serviceName);
        // 本地ip
        domainEventLog.setProcessIp(NetUtil.getLocalhostStr());
        domainEventLog.setBeginProcessTime(beginProcessTime);
        domainEventLog.setProcessResult("processing");
        domainEventLog.setListenerId(listenerId);
        if (domainEventLogPersistence.insert(domainEventLog)) {
            return domainEventLog.getId();
        }
        return null;
    }

    private String getServiceName() {
        final Environment environment = applicationContext.getBean(Environment.class);
        final String property = environment.getProperty("project.name");

        return property;
    }

    public DomainEventLog getDomainEventLog(String eventId, String listenerId) {
        String serviceName = getServiceName();
        return domainEventLogPersistence.getByEventId(eventId, serviceName, listenerId);
    }
}
